﻿// <copyright file="IniFile.cs" company="DotNetLib">
//
// .netlib: http://www.codeplex.com/DotNetLib
//
// Portions of this were part of the CodePlex "Inifile Class Library"
//    project and written by etil, http://www.codeplex.com/site/users/view/etil
//
// This code is released under the New BSD License.
// A copy of this license is available at:
// http://www.codeplex.com/DotNetLib/license
//
// </copyright>

namespace DotNetLib.IO
{
  using System;
  using System.Collections;
  using System.Collections.Generic;
  using System.IO;

  /// <summary>
  /// Represents an INI file.
  /// </summary>
  public sealed class IniFile : IEnumerable<IniFileSection>, IDirty
  {
    /// <summary>
    /// The sections in this INI file.
    /// </summary>
    private IniFileSectionCollection sections = new IniFileSectionCollection();

    /// <summary>
    /// Initializes a new instance of the <see cref="IniFile"/> class.
    /// </summary>
    public IniFile()
    {
    }

    /// <summary>
    /// Gets the <see cref="IniFileSectionCollection"/> of sections contained in this file.
    /// </summary>
    public IniFileSectionCollection Sections
    {
      get { return this.sections; }
    }

    /// <summary>
    /// Gets a value indicating whether this <see cref="IniFile"/> is dirty.
    /// </summary>
    public bool IsDirty
    {
      get { return this.sections.IsDirty; }
      private set { this.sections.IsDirty = value; }
    }

    /// <summary>
    /// Gets the <see cref="IniFileSection"/> with a given name.
    /// </summary>
    /// <param name="name">The name of the section to return.</param>
    public IniFileSection this[string name]
    {
      get { return this.sections[name]; }
    }

    /// <summary>
    /// Loads the <see cref="IniFile"/> from the specified <see cref="Stream"/>.
    /// </summary>
    /// <param name="stream">A <see cref="Stream"/> that represents an INI file to load.</param>
    public void Load(Stream stream)
    {
      using (StreamReader reader = new StreamReader(stream))
      {
        this.Load(reader);
      }
    }

    /// <summary>
    /// Loads the <see cref="IniFile"/> from the specified file.
    /// </summary>
    /// <param name="fileName">The location of the INI file to load.</param>
    public void Load(string fileName)
    {
      using (StreamReader reader = new StreamReader(fileName))
      {
        this.Load(reader);
      }
    }

    /// <summary>
    /// Loads the <see cref="IniFile"/> from the specified <see cref="TextReader"/>.
    /// </summary>
    /// <param name="reader">A <see cref="TextReader"/> that represents an INI file.</param>
    public void Load(TextReader reader)
    {
      IniFileSection currentSection = null;
      while (true)
      {
        string line = reader.ReadLine();
        if (line == null)
        {
          break;
        }

        line = line.Trim();

        if (line.Length == 0)
        {
          break;
        }

        if (line[0] == ';' || line[0] == '#')
        {
          // comment line ignore for now
          continue;
        }

        if (line[0] == '[')
        {
          int index = line.LastIndexOf(']');
          if (index == -1)
          {
            throw new Exception("Error reading section");
          }

          string name = line.Substring(1, index - 1);
          if (this.sections.Contains(name))
          {
            currentSection = this[name];
          }
          else
          {
            currentSection = new IniFileSection(name);
            this.sections.Add(currentSection);
          }
        }
        else
        {
          int index = line.IndexOf('=');
          if (index == -1)
          {
            throw new Exception("Error reading entry");
          }

          string name = line.Substring(0, index).Trim();
          string value = line.Substring(index + 1);

          if (currentSection == null)
          {
            if (this.sections.Contains(string.Empty))
            {
              currentSection = this[string.Empty];
            }
            else
            {
              currentSection = new IniFileSection(string.Empty);
              this.sections.Add(currentSection);
            }
          }

          currentSection.Add(name, value);
        }
      }
    }

    /// <summary>
    /// Saves the <see cref="IniFile"/> to the file.
    /// </summary>
    /// <param name="fileName">The location where you want to save the INI file.</param>
    public void Save(string fileName)
    {
      using (StreamWriter writer = new StreamWriter(fileName))
      {
        this.Save(writer);
      }
    }

    /// <summary>
    /// Saves the <see cref="IniFile"/> to the specified <see cref="Stream"/>.
    /// </summary>
    /// <param name="stream">A <see cref="Stream"/> that you want o save the INI file.</param>
    public void Save(Stream stream)
    {
      using (StreamWriter writer = new StreamWriter(stream))
      {
        this.Save(writer);
      }
    }

    /// <summary>
    /// Saves the <see cref="IniFile"/> to the specified <see cref="TextWriter"/>.
    /// </summary>
    /// <param name="writer">A <see cref="TextWriter"/> where to place the INI file.</param>
    public void Save(TextWriter writer)
    {
      foreach (IniFileSection section in this)
      {
        writer.WriteLine("[{0}]", section.Name);
        foreach (KeyValuePair<string, string> entry in section)
        {
          writer.WriteLine("{0}={1}", entry.Key, entry.Value);
        }
      }
    }

    /// <summary>
    /// Returns the string representation of this <see cref="IniFile"/>.
    /// </summary>
    /// <returns>The string representation of the INI file.</returns>
    public override string ToString()
    {
      return this.sections.ToString();
    }

    /// <summary>
    /// Gets an enumerator to traverse the sections in this <see cref="IniFile"/>.
    /// </summary>
    /// <returns>An enumertor to enumerate over the sections in the INI file.</returns>
    public IEnumerator<IniFileSection> GetEnumerator()
    {
      foreach (IniFileSection section in this.sections)
      {
        yield return section;
      }
    }

    /// <summary>
    /// Gets an enumerator to traverse the sections in this <see cref="IniFile"/>.
    /// </summary>
    /// <returns>An enumertor to enumerate over the sections in the INI file.</returns>
    IEnumerator IEnumerable.GetEnumerator()
    {
      return this.GetEnumerator();
    }
  }
}
